package ltd.flyweight.liquibasehelper.module.jzgk.service;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import liquibase.change.ColumnConfig;
import liquibase.change.core.InsertDataChange;
import liquibase.change.core.UpdateDataChange;
import liquibase.changelog.ChangeSet;
import liquibase.serializer.core.yaml.YamlChangeLogSerializer;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import ltd.flyweight.liquibasehelper.core.consts.CoreConstants;
import ltd.flyweight.liquibasehelper.module.jzgk.dao.dbservice.JzgkSysMenuDbService;
import ltd.flyweight.liquibasehelper.module.jzgk.dao.entity.JzgkSysMenuEntity;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class JzgkSysMenuService implements CoreConstants {

    private final JzgkSysMenuDbService jzgkSysMenuDbService;

    public void runTaskGenMenuFromBigDataLiquibaseYmlScript(long menuId, boolean includeChildren, boolean recursive, List<Long> excludeIds, boolean update) {
        JzgkSysMenuEntity jzgkSysMenuEntity = querySysMenu(menuId, includeChildren, recursive, excludeIds);
        genMenuFromBigDataLiquibaseYmlScript(jzgkSysMenuEntity, update);
        log.info("{}", jzgkSysMenuEntity);
    }

    @SneakyThrows
    private void genMenuFromBigDataLiquibaseYmlScript(JzgkSysMenuEntity jzgkSysMenuEntity, boolean update) {
        log.info("sysMenuEntity={}", jzgkSysMenuEntity);

        String tableName = getTableName(jzgkSysMenuEntity);
        String tablePk = getTablePk(jzgkSysMenuEntity);
        ChangeSet changeSet = new ChangeSet(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmm-01")), "周立山", false, false, null, null, null, true, null);
        if (update) {
            changeSet.setComments("sys_menu修改%s菜单".formatted(jzgkSysMenuEntity.getMenuName()));
            addUpdateDataChange(changeSet, List.of(jzgkSysMenuEntity), tableName, tablePk);
        } else {
            changeSet.setComments("sys_menu增加%s菜单".formatted(jzgkSysMenuEntity.getMenuName()));
            addInsertDataChange(changeSet, List.of(jzgkSysMenuEntity), tableName, tablePk);
        }

        // 创建YAML序列化器
        YamlChangeLogSerializer serializer = new YamlChangeLogSerializer();
        try (OutputStream outputStream = new FileOutputStream("menu_%s.yml".formatted(jzgkSysMenuEntity.getMenuId()))) {
            serializer.write(List.of(changeSet), outputStream);
        }

    }

    private void addUpdateDataChange(ChangeSet changeSet, List<JzgkSysMenuEntity> jzgkSysMenuEntity, String tableName, String tablePk) {
        for (JzgkSysMenuEntity entity : jzgkSysMenuEntity) {
            UpdateDataChange change = new UpdateDataChange();
            change.setTableName(tableName);
            Map<String, Object> fieldAndValues = getAllDatabaseFieldAndValue(entity);
            for (Map.Entry<String, Object> entry : fieldAndValues.entrySet()) {
                if (entry.getKey().equals(tablePk)) {
                    Object pk = fieldAndValues.get(tablePk);
                    if (pk instanceof Number) {
                        change.setWhere("%s = %s".formatted(tablePk, pk));
                    } else {
                        change.setWhere("%s = '%s'".formatted(tablePk, pk));
                    }
                } else {
                    ColumnConfig columnConfig = new ColumnConfig();
                    columnConfig.setName(entry.getKey());
                    columnConfig.setValue(getValue(entry.getValue()));
                    change.addColumn(columnConfig);
                }
            }
            changeSet.addChange(change);
        }
        for (JzgkSysMenuEntity entity : jzgkSysMenuEntity) {
            if (entity.getChildren() != null && !entity.getChildren().isEmpty()) {
                addUpdateDataChange(changeSet, entity.getChildren(), tableName, tablePk);
            }
        }
    }

    private void addInsertDataChange(ChangeSet changeSet, List<JzgkSysMenuEntity> jzgkSysMenuEntity, String tableName, String tablePk) {
        for (JzgkSysMenuEntity entity : jzgkSysMenuEntity) {
            InsertDataChange change = new InsertDataChange();
            change.setTableName(tableName);
            Map<String, Object> fieldAndValues = getAllDatabaseFieldAndValue(entity);
            for (Map.Entry<String, Object> entry : fieldAndValues.entrySet()) {
                ColumnConfig columnConfig = new ColumnConfig();
                columnConfig.setName(entry.getKey());
                columnConfig.setValue(getValue(entry.getValue()));
                change.addColumn(columnConfig);
            }
            changeSet.addChange(change);
        }
        for (JzgkSysMenuEntity entity : jzgkSysMenuEntity) {
            if (entity.getChildren() != null && !entity.getChildren().isEmpty()) {
                addInsertDataChange(changeSet, entity.getChildren(), tableName, tablePk);
            }
        }
    }

    private static String getValue(Object value) {
        if (value == null) {
            return null;
        } else if (value instanceof Date) {
            return UNIVERSAL_DATE_FORMAT.format((Date) value);
        } else {
            return value.toString();
        }
    }

    private String getTablePk(JzgkSysMenuEntity jzgkSysMenuEntity) {
        return "menu_id";
    }

    private String getTableName(JzgkSysMenuEntity jzgkSysMenuEntity) {
        return "sys_menu";
    }

    @SneakyThrows
    private Map<String, Object> getAllDatabaseFieldAndValue(Object object) {
        Map<String, Object> fieldAndValues = new LinkedHashMap<>();
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            boolean isStatic = Modifier.isStatic(field.getModifiers());
            if (isStatic) {
                continue;
            }
            TableId tableId = field.getAnnotation(TableId.class);
            if (tableId != null) {
                String fieldName = tableId.value();
                if (StringUtils.isNotEmpty(fieldName)) {
                    fieldAndValues.put(fieldName, field.get(object));
                    continue;
                }
            }
            TableField tableField = field.getAnnotation(TableField.class);
            if (tableField != null) {
                if (!tableField.exist()) {
                    continue;
                }
                String fieldName = tableField.value();
                if (StringUtils.isNotEmpty(fieldName)) {
                    fieldAndValues.put(fieldName, field.get(object));
                    continue;
                }
            }
            fieldAndValues.put(getFieldName(field), field.get(object));
        }
        return fieldAndValues;
    }

    private String getFieldName(Field field) {
        String fieldName = field.getName();
        return StrUtil.toUnderlineCase(fieldName);
    }

    private JzgkSysMenuEntity querySysMenu(long menuId, boolean includeChildren, boolean recursive, List<Long> excludeIds) {
        JzgkSysMenuEntity jzgkSysMenuEntity = jzgkSysMenuDbService.getById(menuId);
        assert jzgkSysMenuEntity != null;
        if (includeChildren) {
            loadSysMenuChildren(jzgkSysMenuEntity, recursive, excludeIds);
        }
        return jzgkSysMenuEntity;
    }

    private void loadSysMenuChildren(JzgkSysMenuEntity jzgkSysMenuEntity, boolean includeChildren, List<Long> excludeIds) {
        List<JzgkSysMenuEntity> childrenJzgkSysMenuEntityList = jzgkSysMenuDbService.list(
                Wrappers.lambdaQuery(JzgkSysMenuEntity.class)
                        .eq(JzgkSysMenuEntity::getParentId, jzgkSysMenuEntity.getMenuId())
                        .notIn(excludeIds != null && !excludeIds.isEmpty(), JzgkSysMenuEntity::getMenuId, excludeIds)
        );
        jzgkSysMenuEntity.setChildren(childrenJzgkSysMenuEntityList);
        if (includeChildren) {
            if (childrenJzgkSysMenuEntityList != null && !childrenJzgkSysMenuEntityList.isEmpty()) {
                for (JzgkSysMenuEntity childJzgkSysMenuEntity : childrenJzgkSysMenuEntityList) {
                    loadSysMenuChildren(childJzgkSysMenuEntity, includeChildren, excludeIds);
                }
            }
        }
    }
}
